/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.web.io;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.util.Collection;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.genia.toolbox.basics.bean.StreamProgressListener;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.basics.manager.StreamManager;
import com.genia.toolbox.web.helper.TrampolineWebUtils;
import com.genia.toolbox.web.spring.multipart.RequestUploadListener;

/**
 * This class wrapps a <code>HttpServletRequest</code>. It allows multiple
 * call to <code>getInputStream</code> or <code>getReader</code> and return
 * a new object each time. That allows multiple creation of
 * <code>MultiPartServletRequest</code>.
 */
public class MultiQueryableHttpServletRequest
    extends HttpServletRequestWrapper
    implements StreamProgressListener
{

  /**
   * the <code>Log</code> object associated to this class.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(MultiQueryableHttpServletRequest.class);

  /**
   * the content of the original <code>InputStream</code> of this
   * <code>HttpServletRequestWrapper</code>.
   */
  private transient byte[] input;

  /**
   * the decoded content of the original <code>InputStream</code> of this
   * <code>HttpServletRequestWrapper</code>.
   */
  private transient String inputAsString;

  /**
   * the <code>StreamManager</code> used by this class.
   */
  private transient final StreamManager streamManager;



  /**
   * Updates the listeners status information.
   * 
   * @param pBytesRead
   *          The total number of bytes, which have been read so far.
   * @see StreamProgressListener#update(long)
   */
  public void update(long pBytesRead)
  {
    if (getContentLength() > 0) {
      for (RequestUploadListener fileUploadListener : getFileUploadListeners()) {
        if (fileUploadListener.getBindingPath().matcher(TrampolineWebUtils.getRelativePath((HttpServletRequest) getRequest())).matches()) {
          fileUploadListener.update(pBytesRead, getContentLength());
        }
      }
    }
  }

  /**
   * the {@link Collection} of {@link RequestUploadListener} to delegate to.
   */
  private final Collection<RequestUploadListener> fileUploadListeners;



  /**
   * Constructor.
   * 
   * @param streamManager
   *          the <code>StreamManager</code> used by this class
   * @param request
   *          the <code>HttpServletRequest</code> wrapped by this class.
   * @param fileUploadListeners
   *          the {@link Collection} of {@link RequestUploadListener} to delegate
   *          to
   */
  public MultiQueryableHttpServletRequest(final StreamManager streamManager, final HttpServletRequest request, Collection<RequestUploadListener> fileUploadListeners)
  {
    super(request);
    this.streamManager = streamManager;
    this.fileUploadListeners = fileUploadListeners;
    this.input = null;
    this.inputAsString = null;
  }



  /**
   * Retrieves the body of the request as binary data using a
   * <code>ServletInputStream</code>.
   * 
   * @return a <code>ServletInputStream</code> object containing the body of
   *         the request
   * @throws IOException
   *           if an input or output exception occurred
   */
  @Override
  public ServletInputStream getInputStream()
      throws IOException
  {
    try {
      initInput();
    }
    catch (final TechnicalIOException e) {
      throw e.getCause();
    }
    return new DumbServletInputStream(new ByteArrayInputStream(this.input));
  }



  /**
   * Retrieves the body of the request as character data using a
   * <code>BufferedReader</code>.
   * 
   * @return a <code>BufferedReader</code> containing the body of the request
   * @throws IOException
   *           if an input or output exception occurred
   */
  @Override
  public BufferedReader getReader()
      throws IOException
  {
    try {
      return new BufferedReader(new StringReader(getInputAsString()));
    }
    catch (final TechnicalIOException e) {
      throw e.getCause();
    }
  }



  /**
   * return the bodyf of the request as a <code>String</code>.
   * 
   * @return the body of the request as a <code>String</code>
   * @throws TechnicalIOException
   *           if an input or output exception occurred
   */
  private String getInputAsString()
      throws TechnicalIOException
  {
    synchronized (this) {
      initInput();
      if (this.inputAsString == null) {
        String defaultEncoding = getCharacterEncoding();
        try {
          try {
            if (defaultEncoding == null) {
              defaultEncoding = com.genia.toolbox.constants.client.Charset.UTF8;
            }
            Charset.forName(defaultEncoding).newDecoder().onMalformedInput(CodingErrorAction.REPORT).decode(ByteBuffer.wrap(this.input));
            this.inputAsString = new String(this.input, defaultEncoding);
          }
          catch (final CharacterCodingException e) {
            defaultEncoding = com.genia.toolbox.constants.client.Charset.ISO_8859_1;
            this.inputAsString = new String(this.input, defaultEncoding);
          }
          setCharacterEncoding(defaultEncoding);
        }
        catch (final UnsupportedEncodingException e) {
          LOGGER.error(e.getMessage(), e);
        }
      }
      return this.inputAsString;
    }
  }



  /**
   * init the input field with the body of the request.
   * 
   * @throws TechnicalIOException
   *           throws TechnicalIOException
   */
  private void initInput()
      throws TechnicalIOException
  {
    synchronized (this) {
      if (this.input == null) {
        try {
          this.input = streamManager.getBytes(super.getInputStream(), this);
        }
        catch (final IOException e) {
          throw new TechnicalIOException(e);
        }
      }
    }
  }



  /**
   * getter for the fileUploadListeners property.
   * 
   * @return the fileUploadListeners
   */
  public Collection<RequestUploadListener> getFileUploadListeners()
  {
    return fileUploadListeners;
  }

}
